package oop.ex4.crosswords;

import java.nio.CharBuffer;
import java.util.Collection;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;

import oop.ex4.crosswords.CrosswordShape.SlotType;
import oop.ex4.crosswords.strategies.CrosswordStrategy;
import oop.ex4.crosswords.strategies.ExtendedCrosswordDictionary;
import oop.ex4.search.SearchBoard;

/**
 * This is the main class for the Crossboard puzzle. <br>
 * After creating the object, initialize using attachDictionary & attachShape
 * in that order.
 * @author yomach
 * @author mbattat
 */

public class MyCrossword implements Crossword {

	///////////////////////////////////////////////////////
	////////////////////////Constants//////////////////////
	///////////////////////////////////////////////////////

	/**
	 * A boolean representing a vertical entry.
	 */
	public static final boolean VERTICAL = true;

	/**
	 * The dictionary.
	 */
	protected CrosswordDictionary _dict;

	/**
	 * The board.
	 */
	protected CrosswordSquare[][] _board;

	/**
	 * The current strategy.
	 */
	protected CrosswordStrategy _strategy;
	
	/**
	 * The board's current quality.
	 */
	protected int _quality;
	
	///////////////////////////////////////////////////////
	//////////////////////Constructor//////////////////////
	///////////////////////////////////////////////////////

	/**
	 * A default empty constructor. 
	 */
	public MyCrossword() {
		_dict = null;
		_board = null;
		_strategy = null;
		_quality = 0;
	}
	
	/**
	 * A shallow copy constructor, the new board will share the dictionary & 
	 * the list of unused entries with the old one. That means that while it 
	 * will not change when moves are made on the original board, moves on this
	 * board <b>WILL</b> affect the original one! 
	 * @param crossboard The crossboard to copy.
	 */
	private MyCrossword(MyCrossword crossboard) {
		_dict = crossboard._dict;
		copyBoard(crossboard._board);
		_strategy = crossboard._strategy;
		_quality = crossboard._quality;
	}

	///////////////////////////////////////////////////////
	/////////////////////Public Methods////////////////////
	///////////////////////////////////////////////////////

	@Override
	public void attachDictionary(CrosswordDictionary dictionary) {
		_dict = dictionary;
	}

	/**
	 * {@inheritDoc} <br>
	 * Must be called after attachDictionary. Assumes this is true.
	 */
	@Override
	public void attachShape(CrosswordShape shape) {
		_board = new CrosswordSquare[shape.getWidth()][shape.getHeight()];
		for (int x = shape.getWidth()-1; x >= 0; x--) {
			for (int y = shape.getHeight()-1; y >= 0; y--) {
				if (shape.getSlotType(new MyCrosswordPosition(x, y, VERTICAL)) == SlotType.FRAME_SLOT) {
					_board[x][y] = null;
					continue;
				}
				int horizontalSpace = (x == shape.getWidth()-1) ? 1 :
					(_board[x+1][y] == null) ? 1 :
						_board[x+1][y].getEntryLength(!VERTICAL) + 1;
				int verticalSpace = (y == shape.getHeight()-1) ? 1 :
					(_board[x][y+1] == null) ? 1 :
						_board[x][y+1].getEntryLength(VERTICAL) + 1;
				_board[x][y] = new CrosswordSquare(x, y, verticalSpace, horizontalSpace, _dict);
			}
		}
		_strategy = CrosswordStrategy.decideStrategy(_dict, _board);
		_strategy.initializeStrategy(_dict, _board);
	}

	@Override
	public Collection<CrosswordEntry> getCrosswordEntries() {
		Deque<CrosswordEntry> entries = new LinkedList<CrosswordEntry>();
		for (int x = 0; x < _board.length; x++) {
			for (int y = 0; y < _board[0].length; y++) {
				if (_board[x][y] == null) { continue; }
				if (_board[x][y].getEntry(VERTICAL).getTerm() != null) { entries.add(_board[x][y].getEntry(VERTICAL)); }
				if (_board[x][y].getEntry(!VERTICAL).getTerm() != null) { entries.add(_board[x][y].getEntry(!VERTICAL)); }
			}
		}
		return entries;
	}

	@Override
	public void doMove(CrosswordEntry move) {
		_strategy.doMove(move);
		_quality = _strategy.getQuality();
	}

	@Override
	public SearchBoard<CrosswordEntry> getCopy() {
		return new MyCrossword(this);
	}

	@Override
	public Iterator<CrosswordEntry> getMoveIterator() {
		return _strategy.getMoveIterator();
	}

	@Override
	public int getQuality() {
		return _quality;
	}

	@Override
	public int getQualityBound() {
		return _strategy.getQualityBound();
	}

	@Override
	public boolean isBestSolution() {
		return _strategy.isBestSolution();
	}

	@Override
	public void undoMove(CrosswordEntry move) {
		_strategy.undoMove(move);
		_quality = _strategy.getQuality();
	}
	
	@Override
	public String toString() {
		String output = "";
		for (int y = 0; y < _board[0].length; y++) {
			for (int x = 0; x < _board.length; x++) {
				if (_board[x][y] == null) { output += " # "; continue; }
				output += " " + _board[x][y].getCharsAround(VERTICAL).charAt(0) + " ";
			}
			output += "\n";
		}
		output += "\n"; output += "\n";
		for (int y = 0; y < _board[0].length; y++) {
			for (int x = 0; x < _board.length; x++) {
				if (_board[x][y] == null) { output += " # "; continue; }
				output += " " + _board[x][y].getCharsAround(!VERTICAL).charAt(0) + " ";
			}
			output += "\n";
		}
		return output;
	}

	///////////////////////////////////////////////////////
	////////////////////Private Method/////////////////////
	///////////////////////////////////////////////////////
	
	/**
	 * Copies the given board into the current board.
	 * @param board The board to copy.
	 */
	private void copyBoard(CrosswordSquare[][] board) {
		if (board.length == 0) { 
			_board = new CrosswordSquare[0][0];  
			return; 
		}
		_board = new CrosswordSquare[board.length][board[0].length];
		for (int x = 0; x < board.length; x++) {
			for (int y = 0; y < board[0].length; y++) {
				if (board[x][y] != null) { _board[x][y] = new CrosswordSquare(board[x][y]); }
			}
		}
	}

	///////////////////////////////////////////////////////
	//////////////////Public Nested Class//////////////////
	///////////////////////////////////////////////////////

	/**
	 * A nested class representing a square in the board.
	 * The square has 2 entries, one vertically and one horizontally, and several
	 * methods that supply data on the surrounding squares.
	 * The pattern should be have a space character for a wildcard character
	 * or a given character to represent it. <br>
	 * For example, if the dictionary holds the words: "dad", "cat", "bob" & "bat"
	 * then the pattern " a " would return dad, cat & bat, the pattern "b  "
	 * would return bob and bat. <br>
	 * @author yomach
	 * @author mbattat
	 */
	public static class CrosswordSquare {
	
		///////////////////////////////////////////////////////
		///////////////////////Constants///////////////////////
		///////////////////////////////////////////////////////
	
		/**
		 * The integer representing horizontal data.
		 */
		private static final int HORIZONTAL = 0;
	
		/**
		 * The integer representing vertical data.
		 */
		private static final int VERTICAL = 1;
	
		///////////////////////////////////////////////////////
		//////////////////////Data Members/////////////////////
		///////////////////////////////////////////////////////
	
		/**
		 * The vertical & horizontal entries.
		 */
		private CrosswordEntry[] _entry = new CrosswordEntry[2];
	
		/**
		 * The vertical & horizontal characters created by the other entries.
		 */
		private CharBuffer[] _chars = new CharBuffer[2];
	
		/**
		 * The vertical & horizontal quality bound.
		 */
		private int[] _qualityBound = new int[2];
	
		///////////////////////////////////////////////////////
		//////////////////////Constructors/////////////////////
		///////////////////////////////////////////////////////
	
		/**
		 * Creates a new empty square at the given location, with the given dictionary
		 * & with the given spaces around it.
		 * @param x The x coordinate.
		 * @param y The y coordinate.
		 * @param verticalSpace The amount of spaces to the right of the square.
		 * @param horizontalSpace The amount of spaces below the square.
		 * @param dict The dictionary.
		 */
		public CrosswordSquare(int x, int y, int verticalSpace,
				int horizontalSpace, CrosswordDictionary dict) {
			_entry[VERTICAL] = new MyCrosswordEntry(dict, new MyCrosswordPosition(x, y, true));;
			_entry[HORIZONTAL] = new MyCrosswordEntry(dict, new MyCrosswordPosition(x, y, false));
			_chars[VERTICAL] = CharBuffer.allocate(verticalSpace);
			_chars[HORIZONTAL] = CharBuffer.allocate(horizontalSpace);
			for (int i = HORIZONTAL; i <= VERTICAL; i++) {
				for (int j = 0; j < _chars[i].length(); j++) {
					_chars[i].put(j, ExtendedCrosswordDictionary.SPACE);
				}
			}
			_qualityBound[VERTICAL] = 0;
			_qualityBound[HORIZONTAL] = 0;
		}
		
		/**
		 * A copy constructor that creates a new square according to the given one.
		 * @param square The square to copy.
		 */
		public CrosswordSquare(CrosswordSquare square) {
			_entry[VERTICAL] = square._entry[VERTICAL];
			_entry[HORIZONTAL] = square._entry[HORIZONTAL];
			_chars[VERTICAL] = square._chars[VERTICAL];
			_chars[HORIZONTAL] = square._chars[HORIZONTAL];
			_qualityBound[VERTICAL] = square._qualityBound[VERTICAL];
			_qualityBound[HORIZONTAL] = square._qualityBound[HORIZONTAL];
		}
	
		///////////////////////////////////////////////////////
		/////////////////////Public Methods////////////////////
		///////////////////////////////////////////////////////
	
		/**
		 * Returns the requested entry.
		 * @param vertical True for vertical entry, false for horizontal.
		 * @return The requested entry.
		 */
		public CrosswordEntry getEntry(boolean vertical) {
			return _entry[verticalBoolToInt(vertical)];
		}
		
		/**
		 * Sets the entry to the given one.
		 * @param entry The entry to set.
		 */
		public void setEntry(CrosswordEntry entry) {
			_entry[verticalBoolToInt(entry.getPosition().isVertical())] =  entry;
		}
	
		/**
		 * Returns an array of chars representing the letters below or to the
		 * right of the entry until the first blocking point. <br>
		 * The char array returned is NOT a copy and every change imposed in it will
		 * reflect back into the square.
		 * @param vertical True to look vertically, false for horizontally.
		 * @return An array of chars representing the chars around the entry.
		 */
		public CharBuffer getCharsAround(boolean vertical) {
			return _chars[verticalBoolToInt(vertical)];
		}
	
		/**
		 * Returns the length of the requested entry. <br>
		 * @param vertical True to look vertically, false for horizontally.
		 * @return The length of the requested entry
		 */
		public int getEntryLength(boolean vertical) {
			return _chars[verticalBoolToInt(vertical)].length();
		}
	
		/**
		 * Return the quality bound of the square, vertically or horizontally.
		 * @param vertical True to look vertically, false for horizontally.
		 * @return The quality bound.
		 */
		public int getQualityBound(boolean vertical) {
			return _qualityBound[verticalBoolToInt(vertical)];
		}
	
		/**
		 * Sets the vertical / horizontal quality bound to the specified amount.
		 * @param vertical True for vertically, false for horizontally.
		 * @param qualityBound The new quality bound.
		 */
		public void setQualityBound(boolean vertical, int qualityBound) {
			_qualityBound[verticalBoolToInt(vertical)] = qualityBound;
		}
		
		///////////////////////////////////////////////////////
		//////////////////////Static Methods///////////////////
		///////////////////////////////////////////////////////
		
		/**
		 * Counts the number of overlapping letters between a term and a pattern. <br>
		 * The pattern is defined in {@link MyCrossword.CrosswordSquare}. <br>
		 * @param pattern The pattern.
		 * @param term The word to match against the pattern.
		 * @return The number of overlapping letters or -1 if the term dosen't match the pattern.
		 */
		public static int overlappingAmount(CharSequence pattern, String term){
			int overlappingCounter = 0;
			if (term.length() > pattern.length()) { return -1; }
			for (int i = 0; i < term.length(); i++){
				char a = term.charAt(i);
				char b = pattern.charAt(i);
				if (a != ExtendedCrosswordDictionary.SPACE && b == ExtendedCrosswordDictionary.SPACE){
					continue;
				}
				if (a != b) {
					return -1;
				}
				if (a == b){
					overlappingCounter++;
				}
			}
			return overlappingCounter;
		}
		
		@Override
		public String toString() {
			return ("(" + _entry[0].getPosition().getX() + "," + _entry[0].getPosition().getY() 
					+ ") Horziontal Pattern: (" + _chars[0] + ") Vertical Pattern: (" + _chars[1] +")");
		}
	
		///////////////////////////////////////////////////////
		/////////////////////Private Methods///////////////////
		///////////////////////////////////////////////////////
	
		/**
		 * Converts the boolean version of vertical into the integer version.
		 * @param vertical Vertical or not (boolean form).
		 * @return Vertical or not (integer form).
		 */
		private int verticalBoolToInt(boolean vertical) {
			return vertical ? VERTICAL : HORIZONTAL;
		}
	}
}